#include "renderer.h"

#include "context.h"
#include "renderwindow.h"
#include "system/window.h"
#include "declaration.h"
#include "vertexbuffer.h"
#include "indexbuffer.h"
#include "program.h"

namespace dw {
	namespace graffiti {

/*
void Context::bind()
{
	dwAssert(isValid(), "Invalid context");

	if (::wglMakeCurrent(m_deviceContext, m_renderContext) == FALSE)
	{
		dwFail("todo handle this error");
	}
}

void Context::unbind()
{
	dwAssert(isValid(), "Invalid context");

	if (::wglMakeCurrent(NULL, NULL) == FALSE)
	{
		dwFail("todo handle this error");
	}
}
*/

RenderWindow* Renderer::initialize(dw::system::Window* window)
{
	return initialize(window, ContextSetup::Default);
}

RenderWindow* Renderer::initialize(dw::system::Window* window, const ContextSetup& setup)
{
	dwAssert(window != NULL, "Invalid parameter: window is null");
	dwAssert(m_window == NULL, "Invalid internal state");
	dwAssert(m_mainContext == NULL, "Invalid internal state");

	m_window = window;

	m_mainContext = createMainContext(setup);

	return m_mainContext != NULL ? new RenderWindow(window, m_mainContext) : NULL;
}

void Renderer::terminate()
{
}

void Renderer::setProgram(Program* program)
{
	if (m_program != program)
	{
		if ((m_program != NULL) &&
			(program == NULL || m_program->getAttributesHashcode() != program->getAttributesHashcode()))
		{
			m_program->disableAttributes();
			m_program->deactivate();
		}

		m_program = program;

		if (m_program != NULL)
		{
			m_program->activate();
			m_program->enableAttributes();
		}
	}

	m_missingAttributesHashcode = m_program != NULL ? m_program->getAttributesHashcode() : -1;
}

void Renderer::setIndexBuffer(IndexBuffer* buffer)
{
	if (m_indexBuffer != buffer)
	{
		if (m_indexBuffer != NULL)
		{
			m_indexBuffer->unbind();
		}

		m_indexBuffer = buffer;

		if (m_indexBuffer != NULL)
		{
			m_indexBuffer->bind();
		}
	}
}

void Renderer::setVertexBuffer(VertexBuffer* buffer)
{
	dwAssert(m_program != NULL, "Invalid call to setVertexBuffer: no program bound");
	dwAssert(buffer != NULL, "Invalid parameter: buffer is null");

	dw::uint32 bufferAttribHashcode = buffer->getAttributesHashcode();

	if ((m_missingAttributesHashcode & bufferAttribHashcode) == 0)
	{
		dwLogInform("You are trying to bind a vertex buffer that does not contain any required attributes\n");

		return ;
	}
	
	buffer->bind();

	for (dw::uint32 vertexAttribIt = 0; vertexAttribIt < buffer->getAttributesCount(); ++vertexAttribIt)
	{
		const AttributeDeclaration& declaration = buffer->getAttributeDeclaration(vertexAttribIt);
		dw::uint32 offset = buffer->getAttributeOffset(vertexAttribIt);

		dw::uint32 location = 0;
		bool normalize = false;

		if (m_program->hasAttribute(declaration))
		{
			if (m_program->getAttributeLocation(declaration, location, normalize))
			{
				glVertexAttribPointer(
					location,
					attribute::type::elementsCount(declaration.type),
					attribute::glType(attribute::type::elementsType(declaration.type)),
					normalize ? GL_TRUE : GL_FALSE,
					buffer->getVertexSize(), (char *)NULL + offset);

				m_missingAttributesHashcode ^= attribute::semantic::getHashcode(declaration.semantic, declaration.semanticIndex);
			}
		}
	}
}

void Renderer::setUniform(const std::string& name, const void* value)
{
	dwAssert(m_program != NULL, "Invalid call to setUniform: no program bound");

	const ProgramUniformDeclaration* uniform = m_program->getUniformDeclaration(name);

	if (uniform != NULL)
	{
		switch(uniform->type)
		{
		case uniform::type::int32 :
			glUniform1iv(uniform->location, uniform->count, (GLint*)value);
			break ;
		case uniform::type::float32 :
			glUniform1fv(uniform->location, uniform->count, (GLfloat*)value);
			break ;
		case uniform::type::ivec2 :
			glUniform2iv(uniform->location, uniform->count, (GLint*)value);
			break ;
		case uniform::type::vec2 :
			glUniform2fv(uniform->location, uniform->count, (GLfloat*)value);
			break ;
		case uniform::type::ivec3 :
			glUniform3iv(uniform->location, uniform->count, (GLint*)value);
			break ;
		case uniform::type::vec3 :
			glUniform3fv(uniform->location, uniform->count, (GLfloat*)value);
			break ;
		case uniform::type::ivec4 :
			glUniform4iv(uniform->location, uniform->count, (GLint*)value);
			break ;
		case uniform::type::vec4 :
			glUniform4fv(uniform->location, uniform->count, (GLfloat*)value);
			break ;
		case uniform::type::mat2 :
			glUniformMatrix2fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat2x3 :
			glUniformMatrix2x3fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat2x4 :
			glUniformMatrix2x4fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat3 :
			glUniformMatrix3fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat3x2 :
			glUniformMatrix3x2fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat3x4 :
			glUniformMatrix3x4fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat4 :
			glUniformMatrix4fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat4x2 :
			glUniformMatrix4x2fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::mat4x3 :
			glUniformMatrix4x3fv(uniform->location, uniform->count, GL_FALSE, (GLfloat*)value);
			break ;
		case uniform::type::sampler2D :
			glUniform1iv(uniform->location, uniform->count, (GLint*)value);
			break ;
		default :
			dwFail("");
			break ;
		}
	}
}

void Renderer::drawIndexed(primitive::type::types type, dw::uint32 primitivesCount, dw::uint32 primitiveStart /*= 0*/)
{
	dwAssert(m_program != NULL, "Invalid call to drawIndexed: no program bound");
	dwAssert(m_indexBuffer != NULL, "Invalid call to drawIndexed: no index buffer bound");
	dwAssert(m_missingAttributesHashcode == 0, "Invalid call to drawIndexed: attributes are missing");

	dw::uint32 indexStart = primitive::indexCount(type, primitiveStart);
	dw::uint32 indexCount = primitive::indexCount(type, primitivesCount);

	dwAssert(indexStart + indexCount <= m_indexBuffer->getCount(), "Invalid parameters: indexBuffer doesn't hold that many primitives");

	if (m_missingAttributesHashcode != 0)
	{
		m_program->disableAttributes();
	}

	glDrawElements(primitive::glType(type),
		indexCount,
		attribute::glType(m_indexBuffer->getType()), (char *)NULL + indexStart * attribute::type::size(m_indexBuffer->getType()));

	
	if (m_missingAttributesHashcode != 0)
	{
		m_program->enableAttributes();
	}
}

void Renderer::drawIndexedBiased(primitive::type::types type, dw::uint32 primitivesCount, dw::uint32 indexBias, dw::uint32 primitiveStart /*= 0*/)
{
	dwAssert(m_program != NULL, "Invalid call to drawIndexedBiased: no program bound");
	dwAssert(m_indexBuffer != NULL, "Invalid call to drawIndexedBiased: no index buffer bound");
	dwAssert(m_missingAttributesHashcode == 0, "Invalid call to drawIndexed: attributes are missing");

	dw::uint32 indexStart = primitive::indexCount(type, primitiveStart);
	dw::uint32 indexCount = primitive::indexCount(type, primitivesCount);

	dwAssert(indexStart + indexCount <= m_indexBuffer->getCount(), "Invalid parameters: indexBuffer doesn't hold that many primitives");

	if (m_missingAttributesHashcode != 0)
	{
		m_program->disableAttributes();
	}

	glDrawElementsBaseVertex(primitive::glType(type),
		indexCount,
		attribute::glType(m_indexBuffer->getType()), (char *)NULL + indexStart * attribute::type::size(m_indexBuffer->getType()),
		indexBias);

	if (m_missingAttributesHashcode != 0)
	{
		m_program->enableAttributes();
	}
}

void Renderer::pushMatrix(matrix::matrices mode)
{
	dwAssert((dw::uint32)mode < 3, "Invalid parameter: mode is out of bounds");

	m_matrixStacks[(dw::uint32)mode]->push();
}

void Renderer::pushMatrix(matrix::matrices mode, const glm::mat4& mat)
{
	dwAssert((dw::uint32)mode < 3, "Invalid parameter: mode is out of bounds");

	m_matrixStacks[(dw::uint32)mode]->push(mat);
}

void Renderer::popMatrix(matrix::matrices mode)
{
	dwAssert((dw::uint32)mode < 3, "Invalid parameter: mode is out of bounds");

	m_matrixStacks[(dw::uint32)mode]->pop();
}

void Renderer::setMatrix(matrix::matrices mode, const glm::mat4& mat)
{
	dwAssert((dw::uint32)mode < 3, "Invalid parameter: mode is out of bounds");

	m_matrixStacks[(dw::uint32)mode]->set(mat);
}

void Renderer::multMatrix(matrix::matrices mode, const glm::mat4& mat)
{
	dwAssert((dw::uint32)mode < 3, "Invalid parameter: mode is out of bounds");

	m_matrixStacks[(dw::uint32)mode]->mult(mat);
}

const glm::mat4& Renderer::getMatrix(matrix::matrices mode) const
{
	dwAssert((dw::uint32)mode < matrix::matrices_count, "Invalid parameter: mode is out of bounds");

	switch (mode)
	{
	case matrix::model :
	case matrix::view :
	case matrix::proj :
		return m_matrixStacks[(dw::uint32)mode]->top();
	case matrix::modelview :
		return m_modelview = (m_matrixStacks[(dw::uint32)matrix::view]->top() * m_matrixStacks[(dw::uint32)matrix::model]->top());
	case matrix::modelviewproj :
		return m_modelviewproj = (m_matrixStacks[(dw::uint32)matrix::proj]->top() * (m_matrixStacks[(dw::uint32)matrix::view]->top() * m_matrixStacks[(dw::uint32)matrix::model]->top()));
	default :
		dwFail("");
	}

	return m_modelviewproj;
}

Renderer::Renderer()
:	m_mainContext(NULL)
,	m_window(NULL)
,	m_glewIsInitialized(false)
,	m_program(NULL)
,	m_indexBuffer(NULL)
{
	m_matrixStacks[matrix::model] = new MatrixStack(32);
	m_matrixStacks[matrix::view] = new MatrixStack(8);
	m_matrixStacks[matrix::proj] = new MatrixStack(8);
}

Renderer::~Renderer()
{
	delete m_matrixStacks[matrix::model];
	delete m_matrixStacks[matrix::view];
	delete m_matrixStacks[matrix::proj];
}

Context* Renderer::createMainContext(const ContextSetup& setup)
{
	dwAssert(m_window != NULL, "Invalid internal state");
	
    PIXELFORMATDESCRIPTOR pfd = 
    {
        sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd 
        1,								// version number 
        PFD_DRAW_TO_WINDOW |			// support window 
        PFD_SUPPORT_OPENGL |			// support OpenGL 
        PFD_DOUBLEBUFFER,				// double buffered 
        PFD_TYPE_RGBA,					// RGBA type 
        m_window->getBitDepth(),		// 32-bit color depth 
        0, 0, 0, 0, 0, 0,				// color bits ignored 
        0,								// no alpha buffer 
        0,								// shift bit ignored 
        0,								// no accumulation buffer 
        0, 0, 0, 0,						// accum bits ignored 
        setup.depthBits,				// 16-bit z-buffer 
        setup.stencilBits,				// 16-bits stencil buffer 
        0,								// no auxiliary buffer 
        PFD_MAIN_PLANE,					// main layer 
        0,								// reserved 
        0, 0, 0							// layer masks ignored 
    };

    HDC hDC = m_window->getDeviceContext();
	dwAssert(hDC != NULL, "Failed to get Device");

    GLuint pixelFormat = ::ChoosePixelFormat(hDC, &pfd);
	dwAssert(pixelFormat != 0, "Failed to get Device");
    
    PIXELFORMATDESCRIPTOR bestMatch;
    
    bool describePixelFormatSucceed = ::DescribePixelFormat(hDC, pixelFormat, sizeof(pfd), &bestMatch) != FALSE;
	dwAssert(describePixelFormatSucceed, "Failed to get Pixel format");
	dwAssert(bestMatch.cDepthBits >= pfd.cDepthBits, "Failed to get Pixel format");

	bool setPixelFormatSucceed = ::SetPixelFormat(hDC, pixelFormat, &pfd) != FALSE;
	dwAssert(setPixelFormatSucceed, "Failed to set Pixel format");

	HGLRC glRC = ::wglCreateContext(hDC);
	dwAssert(glRC != NULL, "Failed to create Context");

	bool makeCurrentSucceed = ::wglMakeCurrent(hDC, glRC) != FALSE;
	dwAssert(makeCurrentSucceed, "Failed to make current device");

	if (!m_glewIsInitialized)
	{
		m_glewIsInitialized = glewInit() == GLEW_OK;
		dwAssert(m_glewIsInitialized, "Failed to initialize glew");
	}

	Context* context = new Context(setup);

	if (m_glewIsInitialized && setup.majorVersion >= 3)
	{
		bool createAttribsIsSupported = false;

		GLint attribs[9] = {
			WGL_CONTEXT_MAJOR_VERSION_ARB, setup.majorVersion,
			WGL_CONTEXT_MINOR_VERSION_ARB, setup.minorVersion,
			WGL_CONTEXT_FLAGS_ARB,
				(setup.flags & ContextSetup::Flag_ForwardCompatible) ? WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB : 0 |
				(setup.flags & ContextSetup::Flag_Debug) ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
			WGL_CONTEXT_PROFILE_MASK_ARB,
				(setup.profile & ContextSetup::Profile_Core) ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : 0 |
				(setup.profile & ContextSetup::Profile_Compatible) ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : 0,
			0
		};

		if (wglewIsSupported("WGL_ARB_create_context_profile"))
		{
			createAttribsIsSupported = true;
		}
		else if (wglewIsSupported("WGL_ARB_create_context"))
		{
			attribs[6] = 0;
			attribs[7] = 0;

			createAttribsIsSupported = true;
		}

		if (createAttribsIsSupported)
		{
			HGLRC glAdvancedRC = wglCreateContextAttribsARB(hDC, NULL, attribs);
			
			dwAssert(glAdvancedRC != NULL, "Failed to create advanced Context");

			if (glAdvancedRC != NULL)
			{
				::wglMakeCurrent(NULL, NULL);
				::wglDeleteContext(glRC);
				::wglMakeCurrent(hDC, glAdvancedRC);

				glRC = glAdvancedRC;
			}
		}
	}

	context->m_deviceContext = hDC;
	context->m_renderContext = glRC;

	return context;
}

	} // namespace graffiti
} // namespace dw
